package com.zcl.seckill.service.impl;

import com.zcl.seckill.bean.SecondKillReq;
import com.zcl.seckill.constants.Constants;
import com.zcl.seckill.service.SecondKillService;
import com.zcl.seckill.utils.RedisUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;

/**
 * @author 周成龙
 * @date 2021-01-22 15:09
 * description  这里假定商品名称为apple 商品数量为10000
 *              
 */
@Service
public class SecondKillServiceImpl implements SecondKillService {
    @Autowired
    RedisUtils redisUtils;
    //锁操作
    RLock lock=new RLock();
    //商品相关的操作
    Goods goods=new Goods();
    /**
     * description  用户参与秒杀活动，这里如果只是使用分布式锁
     *              加分布式锁，锁的名称为商品名
      * @param secondKillReq
     * return 
     * author 周成龙
     * createTime 2021/1/22 15:20
     **/
    @Override
    public Boolean secondKill(SecondKillReq secondKillReq) {
        //锁的名称
        final String lockName=secondKillReq.getGoodsName()+"_lock";
        //商品名称
        final String goodsName=secondKillReq.getGoodsName();
        //加锁
        lock.lock(lockName);
        //加锁成功，获取商品数量
        Long goodsNumber = goods.getGoodsNumber(goodsName);
        if(goodsNumber<secondKillReq.getPurchaseNumber()){
            return false;
        }
        //更新商品数量
        Boolean secondKillResult = goods.updateGoodsNumber(goodsName, goodsNumber - secondKillReq.getPurchaseNumber());
        //解锁
        lock.unLock(lockName);
        return secondKillResult;
    }

    /**
     * description
     * 为了提升接口的响应速度，我们这里使用分段加锁
     * 10000个商品分为10个库存段，每个库存段有1000个商品
     * 一个线程进来之后，随机在10个库存段中查找一个库存，如果库存充足，则直接扣减该段库存
     * 如果库存不足，则遍历10个库存段，看有没有库存充足的，如果有直接扣减库存
     * 如果没有单个库存段的库存充足的，看之前拿到的库存是否全是0，
     * 如果全是0，就直接返回
     * 如果有库存，则依次加锁，将所有的库存加起来，看是否库存是充足的
     * 如果不是则直接返回
     * 如果是就从左到右依次扣减库存，释放锁
      * @param secondKillReq
     * return
     * author 周成龙
     * createTime 2021/1/22 16:23
     **/
    public Boolean secondKill1(SecondKillReq secondKillReq){
        //获取随机的库存端
        int stockSegmentSeq = new Random().nextInt(Constants.STOCK_NUMBER) + 1;
        //生成锁的名称
        String lockName=secondKillReq.getGoodsName()+"_lock_"+stockSegmentSeq;
        //生成库存名
        String goodsSegmentSeqName=secondKillReq.getGoodsName()+"_"+stockSegmentSeq;
        //尝试加锁
        lock.lock(lockName);
        //获取该库存段的库存
        Long goodsSegmentSeqNumber = goods.getGoodsNumber(goodsSegmentSeqName);
        if(goodsSegmentSeqNumber==0){
            lock.unLock(lockName);
            //如果该段库存为0，就应该遍历所有的库存看是否充足
            for (int i = 1; i <= Constants.STOCK_NUMBER; i++) {
                if(stockSegmentSeq==i){
                    continue;
                }
                lockName=secondKillReq.getGoodsName()+"_lock_"+i;
                goodsSegmentSeqName=secondKillReq.getGoodsName()+"_"+i;
                //尝试加锁
                lock.lock(lockName);
                //获取该段库存值
                goodsSegmentSeqNumber = goods.getGoodsNumber(goodsSegmentSeqName);
                //找出剩余库存最大的库存段
                if(goodsSegmentSeqNumber!=0){
                    stockSegmentSeq=i;
                    break;
                }else{
                    lock.unLock(lockName);
                }
            }
            //如果所有的库存全部是0，就直接退出
            if(goodsSegmentSeqNumber==0){
                return false;
            }
        }


        /**
         * 以上的所有只是为了找出一个非0库存的位置
         */


        //生成现在加锁的锁的名称
        lockName=secondKillReq.getGoodsName()+"_lock_"+stockSegmentSeq;
        //生成当前库存的名臣
        goodsSegmentSeqName=secondKillReq.getGoodsName()+"_"+stockSegmentSeq;
        //判断此时取得的商品库存是否>想买的库存
        if(goodsSegmentSeqNumber>=secondKillReq.getPurchaseNumber()){
            //修改库存，生成订单
            goods.updateGoodsNumber(goodsSegmentSeqName,goodsSegmentSeqNumber-secondKillReq.getPurchaseNumber());
            //释放锁
            lock.unLock(lockName);
            return true;
        }

        /**
         * 以上是做单个库存可以满足购买需求
         * 以下是单个库存满足不了购买需求，而合并库存
         */

        //走到这里说明，还有库存当时单个库的库存不足
        //将所有库库存相加
        //用来存锁名/库存名的键值对
        HashMap<String,String> map=new HashMap<>();
        for (int i = 0; i < Constants.STOCK_NUMBER; i++) {
            if(stockSegmentSeq==i){
                continue;
            }
            //生成现在加锁的锁的名称
            lockName=secondKillReq.getGoodsName()+"_lock_"+i;
            //生成当前库存的名称
            goodsSegmentSeqName=secondKillReq.getGoodsName()+"_"+i;
            //尝试加锁
            lock.lock(lockName);
            //获取当前库存
            Long goodsNumber = goods.getGoodsNumber(goodsSegmentSeqName);
            if(goodsNumber!=0){
                map.put(lockName,goodsSegmentSeqName);
                goodsSegmentSeqNumber+=goodsNumber;
            }else{
                lock.unLock(lockName);
            }
            //已经凑齐库存
            if(goodsSegmentSeqNumber>=secondKillReq.getPurchaseNumber()){
                break;
            }
        }
        if(goodsSegmentSeqNumber<secondKillReq.getPurchaseNumber()){
            //这里应该吧之前加的锁释放掉
            lock.unLock(lockName);
            for (Map.Entry<String, String> stringLongEntry : map.entrySet()){
                String key = stringLongEntry.getKey();
                lock.unLock(key);
            }
            //说明此此时的全部库存已经不够了
            return false;
        }
        //到这里说明，加一加库存还是够的
        //下面进行扣减库存
        //生成现在加锁的锁的名称
        lockName=secondKillReq.getGoodsName()+"_lock_"+stockSegmentSeq;
        //生成当前库存的名臣
        goodsSegmentSeqName=secondKillReq.getGoodsName()+"_"+stockSegmentSeq;
        //首先将一开始的那个库存全部扣减，并且是否锁
        goods.updateGoodsNumber(goodsSegmentSeqName,0L);
        lock.unLock(lockName);

        //下面依次扣减
        for (Map.Entry<String, String> stringLongEntry : map.entrySet()) {
            lockName=stringLongEntry.getKey();
            goodsSegmentSeqName=stringLongEntry.getValue();
            //获取库存
            Long goodsNumber = goods.getGoodsNumber(goodsSegmentSeqName);
            //更新库存
            if(goodsSegmentSeqNumber>=goodsNumber){
                goods.updateGoodsNumber(goodsSegmentSeqName,0L);
            }else{
                goods.updateGoodsNumber(goodsSegmentSeqName,goodsNumber-goodsSegmentSeqNumber);
            }
            goodsSegmentSeqNumber-=goodsNumber;
            if(goodsSegmentSeqNumber<=0){
                break;
            }
            //释放锁
            lock.unLock(lockName);
        }
        return true;
    }

    /**
     * description 分布式加锁
     * author 周成龙
     * createTime 2021/1/22 16:37
     **/
    class RLock{
        /**
         * description 加锁
          * @param lockName redis中分布式锁的名称
         * return
         * author 周成龙
         * createTime 2021/1/22 16:54
         **/
        public void lock(String lockName){
            while (true){
                Boolean lockStatus = lock.getLockStatus(lockName);
                if(!lockStatus){
                    redisUtils.set(lockName,true);
                    break;
                }
            }
        }

        /**
         * description 解锁
          * @param lockName redis中分布式锁的名称
         * return
         * author 周成龙
         * createTime 2021/1/22 16:55
         **/
        public void unLock(String lockName){
            redisUtils.set(lockName,false);
        }

        /**
         * description 按照锁的名称查询锁的状态
          * @param lockName redis中分布式锁的名称
         * return
         * author 周成龙
         * createTime 2021/1/22 16:55
         **/
        public Boolean getLockStatus(String lockName){
            Object o = redisUtils.get(lockName);
            Boolean falseFlag = new Boolean(false);
            if(o==null||falseFlag.equals(Boolean.parseBoolean(o.toString()))){
                return false;
            }
            return true;
        }
    }
    /**
     * description 商品相关的操作
     * author 周成龙
     * createTime 2021/1/22 16:56
     **/
    class Goods{
        /**
         * description 按照商品的名称获取商品的数量
          * @param goodsName redis商品的名称
         * return
         * author 周成龙
         * createTime 2021/1/22 16:56
         **/
        public Long getGoodsNumber(String goodsName){
            Object o = redisUtils.get(goodsName);
            if(o==null){
                return 0L;
            }
            return Long.parseLong(o.toString());
        }
        /**
         * description 更新商品的数量
          * @param goodsName redis商品的名称
         * @param newGoodsNumber 更新后商品的数量
         * return
         * author 周成龙
         * createTime 2021/1/22 16:57
         **/
        public Boolean updateGoodsNumber(String goodsName,Long newGoodsNumber){
            return redisUtils.set(goodsName,newGoodsNumber);
        }
    }
}
